<?php
/*
 * Copyright (C)
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * @license http://www.gnu.org/licenses/gpl.txt
 * @author Te Hsiung Tseng <cedriccefc2002@gmail.com> 
 * @copyright Copyright (c) 2011, Te Hsiung Tseng
 * @version 0.8
 */
class_alias('AliasNameSpace','A');
define('A_URLCODE',A::CODEFIX.A::CALL_URL_T.A::CODEFIX);
if(!gc_enabled())gc_enable();
//--------------------------------------------------------------------------------------------------------------------//
function __autoload($name) 
{
    AliasNameSpace::autoload_dirfix($name);
}
class AliasNameSpace
{
	//預設產生檔案的目錄
	const UNIX_SCRIPT_FILE_TEMP_DIR='/dev/shm';
	const WINDOWS_SCRIPT_FILE_TEMP_DIR='C:\tmp';
	const DEBUG=false;
	//若要使用memcache 快取可在此設定
	const CONFIG_MEMCACHED_HOST='127.0.0.1';
	const CONFIG_MEMCACHED_PORT=11211;
	const CONFIG_MEMCACHED_TIMEOUT=3600;
	const CODEFIX='::';
	const CALL_ORIGIN_CLASS_T='ORIGIN';
	const CALL_URL_T='URL';
    const readCache=false;//memcache 讀取快取（直接跳過運算），開發時請勿使用
    const writeCache=false;//memcache 寫入快取
    const globleval='*';
	const phar_filename='lib.phar';
	const windows_path_encode="CP950";//在windows下路徑無法使用utf8必須轉碼
	private static $FILE_SHLIB_SUFFIX=array('.php','.html','.js','.css','.png','.jpg','.gif');//可以支援的檔名與順序
    private static $aliasNameSpace=array();
    private static $aliasNameSpaceClass=array();
    private static $memcache;
	public $cache_block_list=array();//特定不快取名單
    private static function loadcache($name)
    {
		if(isset($cache_block_list[$name]))
		{
			return false; 
		}
		else
		{
			if(self::writeCache||self::readCache)
			{
				self::$memcache = new Memcache;
				self::$memcache->connect(self::CONFIG_MEMCACHED_HOST, self::CONFIG_MEMCACHED_PORT) or die ("Could not connect");
				if(self::readCache)
				{
					$tempfile=self::$memcache->get(md5(__FILE__.__CLASS__.$name),MEMCACHE_COMPRESSED);
					if($tempfile==false)
					{
						return false;
					}
					else
					{
						require_once $tempfile;
						return true;
					}
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}
		}
    }
    private static function listNameSpace($namespacename)
    {
        $r=array();
        if(isset(self::$aliasNameSpace[$namespacename]))
        {
            $alias=self::$aliasNameSpace[$namespacename];
            foreach($alias as $key=>$val)
            {
                $r[]=$val;
                if($val['aliasnamespace']==$namespacename)
                {
                    continue;
                }
                else
                {  
                    foreach(self::listNameSpace($val['aliasnamespace']) as $v)
                    {
                        $r[]=$v;
                    }
                }
            }
        }
        return $r;
    }
    private static function listaliasNameSpaceClass($namespacename,$class)
    {
        $r=array();
        
        if(isset(self::$aliasNameSpaceClass[$namespacename][self::globleval]))//繼承全域而來的優先權較小
        {
            $alias=self::$aliasNameSpaceClass[$namespacename][self::globleval];
            foreach($alias as $key=>$val)
            {
                if($val['aliasnamespace']==$namespacename)
                {
                    continue;
                }
                else
                {
                    foreach(self::listaliasNameSpaceClass($val['aliasnamespace'],$class) as $v)
                    {
                        $r[]=$v;
                    }
                }
            }
        }
        if(isset(self::$aliasNameSpaceClass[$namespacename][$class]))
        {
            $alias=self::$aliasNameSpaceClass[$namespacename][$class];
            foreach($alias as $key=>$val)
            {
                $r[]=$val;
                if($val['aliasnamespace']==$namespacename&&$val['aliasclass']==$class)
                {
                    continue;
                }
                else
                {  
                    foreach(self::listaliasNameSpaceClass($val['aliasnamespace'],$val['aliasclass']) as $v)
                    {
                        $r[]=$v;
                    }
                }
            }
        }        
        return $r;
    }
    private static function toNameSpaceClass($name)
    {
        $pos=strrpos($name,"\\");
        $namespacename=substr($name,0,$pos);
        $classname=substr($name,$pos+1);
        return array('namespacename'=>$namespacename,'classname'=>$classname);
    }
    private static function file_exists($namespacename,$classname,&$path)
    {
		
		if(self::isCode($namespacename))
		{
			$path=$namespacename;
			return true;
		}
		elseif(self::isCode($classname))
		{
			$path=$classname;
			return true;
		}
		else
		{
			$pathbase=self::iconv(__DIR__.DIRECTORY_SEPARATOR.
				str_replace("\\",DIRECTORY_SEPARATOR,$namespacename).
				DIRECTORY_SEPARATOR.$classname);
			foreach(self::$FILE_SHLIB_SUFFIX as $v)
			{
				if(file_exists($pathbase.$v))
				{
					$path=$pathbase.$v;
					return true;
				}
			}
			if(extension_loaded('phar'))
			{
				$path=self::iconv('phar://'.__DIR__.DIRECTORY_SEPARATOR).$namespacename.'/'.$classname;
				if(file_exists($path))
				{
					return true;
				}
			}
		}
		return false;		
    }
	private static function isCode($path)
	{
		if(substr($path,0,strlen(self::CODEFIX))==self::CODEFIX)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	private static function gettempfile($name,$path)
	{		
		if(PHP_SHLIB_SUFFIX=='dll')
		{
			$p=self::WINDOWS_SCRIPT_FILE_TEMP_DIR.DIRECTORY_SEPARATOR;			
		}
		else
		{			
			$p=self::UNIX_SCRIPT_FILE_TEMP_DIR.DIRECTORY_SEPARATOR;
		}
		if(self::isCode($path))
		{
			return $p.md5($name.$path.'@'.filemtime(__FILE__)).'.php';
		}
		else
		{
			return $p.md5($name.$path.'@'.filemtime(__FILE__).filemtime($path)).'.php';
		}
	}
    private static function includefile($name,$classname,$namespacename,$oldclassname,$path)
    {
		$tempfile=self::gettempfile($name,$path);
		if(file_exists($tempfile))
		{
		}
		else
        {
			if(self::isCode($path))
			{
				if(empty($classname)||empty($namespacename))
				{
					return false;
				}
				else
				{
					if($path==self::CODEFIX.self::CALL_ORIGIN_CLASS_T.self::CODEFIX)
					{
						$contents="<?php namespace {$namespacename};class {$classname} extends \\{$classname}{}";
					}
					elseif(substr($path,0,strlen(self::CODEFIX.self::CALL_URL_T.self::CODEFIX))==self::CODEFIX.self::CALL_URL_T.self::CODEFIX)
					{
						//此處會用到 class urlClass
						$url=substr($path,strlen(self::CODEFIX.self::CALL_URL_T.self::CODEFIX));
						//$urlclassname=md5($classname);
						$contents="<?php namespace {$namespacename};
							class {$classname} extends urlClass
							{
								protected \$url='{$url}';
							}							
						";
						//class_alias('url_{$urlclassname}','{$classname}');//無法使用(似乎是autiload機制問題)
						/*frist USE OR class_alias then __autoload */
					}
				}
			}
			else
			{
				$contents=file_get_contents($path);
				//此處會用到 class HtmlFile
				if(substr($path,-5)=='.html')
				{
					$title='';
					$constlist='';
					if(extension_loaded('tidy'))
					{
						$config = array('indent' => TRUE,'output-xhtml' => TRUE,'wrap' => 200);
						$tidy = new tidy;
						$tidy->parseString($contents, $config, 'utf8');
						$head = $tidy->head();
						if($head->hasChildren())
						{
							
							foreach($head->child as $node)
							{
								if($node->isHtml()&&$node->hasChildren())
								{
									$value=htmlspecialchars($node->child[0]->value,ENT_QUOTES,'UTF-8');
									$constlist.="const {$node->name}='{$value}';".PHP_EOL;
								}
							}
						}
					}
					$contents="<?php namespace {$namespacename};
						class {$classname} extends HtmlFile
						{
							const name='{$oldclassname}';
							{$constlist}
							public function __construct()
							{
								?>".$contents.'<?php 
							}
						}';
				}
				elseif(substr($path,-4)=='.php')
				{
					if($oldclassname!=$classname)
					{
						$search="class {$oldclassname}";
						$replace="class {$classname}";
						$contents = str_replace($search,$replace,$contents,$count);
						if($count!=1)
						{
							die("類別取代錯誤:{$name}=>由{$search}到{$replace}");
						}
					}
					$contents="<?php namespace {$namespacename};?>".$contents;
				}
				else
				{
					//此處會用到 class ResourceFile
					$filemime='';
					if(extension_loaded('fileinfo'))
					{
						$finfo = new finfo(FILEINFO_MIME_TYPE);
						$filemime=$finfo->file($path);
					}
					if($filemime=='') $filemime="application/octet-stream";
					$length=strlen($contents);
					$mtime=filemtime($path);
					$contents="<?php namespace {$namespacename};
						class {$classname} extends ResourceFile
						{
							protected \$mtime={$mtime};
							protected \$length={$length};
							protected \$mime='{$filemime}';
							protected \$path='{$path}';
						}";
				}
			}
			file_put_contents($tempfile,$contents);
        }
		if(self::writeCache)
        {
            echo self::$memcache->set(md5(__FILE__.__CLASS__.$name),$tempfile, MEMCACHE_COMPRESSED,self::CONFIG_MEMCACHED_TIMEOUT) ;
        }
        require_once $tempfile;
    }
    public static function removeNameSpace($namespace,$aliasnamespace,$priority=null)
    {
        if(isset(self::$aliasNameSpace[$namespace]))
        {
            foreach(self::$aliasNameSpace[$namespace] as $key=>$val)
            {
                if($val['aliasnamespace']==$aliasnamespace)
                {
                    if($priority===null)
                    {
                        unset(self::$aliasNameSpace[$namespace][$key]);
                        self::removeNameSpaceClass($namespace,$aliasnamespace,self::globleval);
                        return true;
                    }
                    elseif($key==$priority)
                    {
                        return false;
                    }
                    else
                    {
                        unset(self::$aliasNameSpace[$namespace][$key]);
                        self::removeNameSpaceClass($namespace,$aliasnamespace,self::globleval);
                        return true;
                    }
                }
            }
        }
        return true;
    }
    public static function addNameSpace($namespace,$aliasnamespace,$priority=1000)
    {
        if(isset(self::$aliasNameSpace[$namespace]))
        {
            if(self::removeNameSpace($namespace,$aliasnamespace,$priority))
            {
                if(isset(self::$aliasNameSpace[$namespace][$priority]))
                {
                    self::addNameSpace($namespace,self::$aliasNameSpace[$namespace][$priority]['aliasnamespace'],$priority-1);//預設覆蓋之前設定
                    self::$aliasNameSpace[$namespace][$priority]['aliasnamespace']=$aliasnamespace;
                    ksort(self::$aliasNameSpace[$namespace]);
                    self::addNameSpaceClass($namespace,$aliasnamespace,self::globleval,$priority);
                    return true;
                }
                else
                {
                    self::$aliasNameSpace[$namespace][$priority]['aliasnamespace']=$aliasnamespace;
                    ksort(self::$aliasNameSpace[$namespace]);
                    self::addNameSpaceClass($namespace,$aliasnamespace,self::globleval,self::globleval,$priority);
                    return true;
                }
            }
        }
        else
        {
            self::$aliasNameSpace[$namespace][$priority]['aliasnamespace']=$aliasnamespace;
            self::addNameSpaceClass($namespace,$aliasnamespace,self::globleval,self::globleval,$priority);
            return true;
        }        
    }
    public static function removeNameSpaceClass($namespace,$aliasnamespace,$class,$aliasclass=null,$priority=null)
    {
        if($aliasclass==null) $aliasclass=$class;
        if(isset(self::$aliasNameSpaceClass[$namespace][$class]))
        {
            foreach(self::$aliasNameSpaceClass[$namespace][$class] as $key=>$val)
            {
                if($val['aliasnamespace']==$aliasnamespace&&$val['aliasclass']==$aliasclass)
                {
                    if($priority===null)
                    {
                        unset(self::$aliasNameSpaceClass[$namespace][$class][$key]);
                        return true;
                    }
                    elseif($key==$priority)
                    {
                        return false;
                    }
                    else
                    {
                        unset(self::$aliasNameSpaceClass[$namespace][$class][$key]);
                        return true;
                    }
                }
            }
        }
        return true;
    }
    public static function addNameSpaceClass($namespace,$aliasnamespace,$class,$aliasclass=null,$priority=1000)
    {
        if($aliasclass==null) $aliasclass=$class;
        if(isset(self::$aliasNameSpaceClass[$namespace][$class]))
        {
            if(self::removeNameSpaceClass($namespace,$aliasnamespace,$aliasclass,$priority))
            {
                if(isset(self::$aliasNameSpaceClass[$namespace][$class][$priority]))
                {
                    self::addNameSpaceClass(
                        $namespace,
                        self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasnamespace'],
                        self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasclass'],
                        self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasclass'],
                        $priority-1 //預設覆蓋之前設定
                    );
                    self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasnamespace']=$aliasnamespace;
                    self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasclass']=$aliasclass;
                    ksort(self::$aliasNameSpaceClass[$namespace][$class]);
                    return true;
                }
                else
                {
                    self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasnamespace']=$aliasnamespace;
                    self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasclass']=$aliasclass;
                    ksort(self::$aliasNameSpaceClass[$namespace][$class]);
                    return true;
                }     
            }       
        }
        else
        {
            self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasnamespace']=$aliasnamespace;
            self::$aliasNameSpaceClass[$namespace][$class][$priority]['aliasclass']=$aliasclass;
            return true;
        }
    }
    public static function autoload_dirfix($name)
    {
        $tmpdir=getcwd();
        if($tmpdir!=__DIR__)
        {
            chdir(__DIR__);            
            self::autoload($name);
            chdir($tmpdir);
        }
        else
        {
            self::autoload($name);
        }
    }
    public static function autoload($name)
    {
        if(self::loadcache($name))
        {
            return true;
        }
        else
        {
            $names=self::toNameSpaceClass($name);
            self::addNameSpace($names['namespacename'],$names['namespacename'],0);
            self::addNameSpaceClass($names['namespacename'],$names['namespacename'],$names['classname'],$names['classname'],0);
            if(self::DEBUG) print_r($names);
            if(self::DEBUG) print_r(self::$aliasNameSpace);
            if(self::DEBUG) print_r(self::$aliasNameSpaceClass);
            if(self::DEBUG) echo $name.PHP_EOL;
            $pathlist=array();
            foreach(array_reverse(self::listaliasNameSpaceClass($names['namespacename'],$names['classname'])) as $alias)
            {
                if(self::file_exists($alias['aliasnamespace'],$alias['aliasclass'],$path))
                {
                    self::includefile($name,$names['classname'],$names['namespacename'],$alias['aliasclass'],$path);
                    if(self::DEBUG) echo "listaliasNameSpaceClass:載入類別：{$name}".PHP_EOL.self::riconv(implode(PATH_SEPARATOR,$pathlist)).PATH_SEPARATOR.self::riconv($path).PHP_EOL;
                    return true;
                }
                else
                {
                    $pathlist[]=$path;
                }            
            }
            foreach(array_reverse(self::listNameSpace($names['namespacename'])) as $alias)
            {
                if(self::file_exists($alias['aliasnamespace'], $names['classname'],$path))
                {
                    self::includefile($name,$names['classname'],$names['namespacename'],$names['classname'],$path);
                    if(self::DEBUG) echo "listNameSpace:載入類別：{$name}".PHP_EOL.self::riconv(implode(PATH_SEPARATOR,$pathlist)).PATH_SEPARATOR.self::riconv($path).PHP_EOL;
                    return true;
                }
                else
                {
                    $pathlist[]=$path;
                }          
            }
            if(self::file_exists($names['namespacename'],$names['classname'],$path))
            {
                self::includefile($name,$names['classname'],$names['namespacename'],$names['classname'],$path);
                if(self::DEBUG) echo "載入類別：{$name}".PHP_EOL.self::riconv(implode(PATH_SEPARATOR,$pathlist)).PATH_SEPARATOR.self::riconv($path).PHP_EOL;
                return true;
            }
			elseif(class_exists($names['classname'],false))
			{
				$path=self::CODEFIX.self::CALL_ORIGIN_CLASS_T.self::CODEFIX;
				self::includefile($name,$names['classname'],$names['namespacename'],$names['classname'],$path);
				if(self::DEBUG) echo "載入原始類別：{$name}".PHP_EOL.self::riconv(implode(PATH_SEPARATOR,$pathlist)).PATH_SEPARATOR.$path.PHP_EOL;
                return true;
			}
            else
            {
                 if(self::DEBUG) die("無法載入類別：{$name}".PHP_EOL.self::riconv(implode(PATH_SEPARATOR,$pathlist)).")".PHP_EOL);
				 return false;
            }
        }
    }
    public static function scanClass($namespacename)
    {
        $r=array();
        //if(self::DEBUG) echo $namespacename.PHP_EOL;
        if(self::DEBUG) print_r(self::$aliasNameSpace);
        if(self::DEBUG) print_r(self::$aliasNameSpaceClass);
        $path=__DIR__.DIRECTORY_SEPARATOR.str_replace("\\",DIRECTORY_SEPARATOR,$namespacename);
        if(file_exists($path))
        {
            $classdir = dir($path);
            while (false !== ($entry = $classdir->read()))
            {
                if(substr($entry,-4)=='.php')
                {
                   $r[substr($entry,0,-4)]='origin'; 
                }
            }
            $classdir->close();
        }
        foreach(self::listNameSpace($namespacename) as $alias)
        {   
            if($alias['aliasnamespace']==$namespacename) continue;
            foreach(self::scanClass($alias['aliasnamespace']) as $key => $val)
            {
                if($key==self::globleval)
                {
                    continue;
                }
                $r[$key]='aliasNameSpace';
            }
        }
        if(isset(self::$aliasNameSpaceClass[$namespacename]))
        {
            foreach(self::$aliasNameSpaceClass[$namespacename] as $class=>$alias)
            {
                if($class==self::globleval)
                {
                    continue;
                }
                $r[$class]='aliasNameSpaceClass';
            }
        }
        return $r;
    }
    public static function toJson()
    {
        return json_encode(array('aliasNameSpaceClass'=>self::$aliasNameSpaceClass,'aliasNameSpace'=>self::$aliasNameSpace));
    }
    public static function fromJson($json)
    {
        $jsonarray=json_decode($json);
        self::$aliasNameSpaceClass=$jsonarray['aliasNameSpaceClass'];
        self::$aliasNameSpace=$jsonarray['aliasNameSpace'];
    }
    public static function reset($namespacename=null,$classname=null)
    {
		if($namespacename!=null&&$classname!=null)
		{
			unset(self::$aliasNameSpaceClass[$namespacename][$classname]);
		}
		elseif($namespacename!=null)
		{
			unset(self::$aliasNameSpaceClass[$namespacename]);
			unset(self::$aliasNameSpace[$namespacename]);
		}
		else
		{
			self::$aliasNameSpaceClass=array();
			self::$aliasNameSpace=array();
		}
    }
	public static function riconv($s)
	{
		if(PHP_SHLIB_SUFFIX=='dll')
		{
			return iconv(self::windows_path_encode,'utf-8',$s);
		}
		else
		{
			return $s;
		}
	}
	public static function iconv($s)
	{
		if(PHP_SHLIB_SUFFIX=='dll')
		{
			return @iconv('utf-8',self::windows_path_encode,$s);
		}
		else
		{
			return $s;
		}
	}
    public static function N()
    {
        switch(func_num_args())
        {
            case 0:
                return self::reset();
            case 2:
                return self::addNameSpace(func_get_arg(0),func_get_arg(1));
            case 3:
                return self::addNameSpaceClass(func_get_arg(0),func_get_arg(1),func_get_arg(2));
            case 4:
                return self::addNameSpaceClass(func_get_arg(0),func_get_arg(1),func_get_arg(2),func_get_arg(3));
        }   
    }
}